﻿#include "NPPPySide.h"

static const wchar_t *Plugin_Name = L"NPPPySide";

static void NPPPySideMainMenu() {
    NPPPySide::instance()->SetMgrVisible(true);
    NPPPySide::instance()->ShowPySideWindow();
}

void _setInfo(NppData notpadPlusData)
{
    NPPPySide::instance(&notpadPlusData);
}

const wchar_t * _getName()
{
    return Plugin_Name;
}

FuncItem *_getFuncsArray(int *nbF)
{
    QVector<FuncItem> *funcItems = new QVector<FuncItem>();

    FuncItem item;
    wcscpy_s(item._itemName, L"NPPPySide Mgr Panel");
    item._pFunc = NPPPySideMainMenu;
    item._init2Check = FALSE;
    item._pShKey = nullptr;
    funcItems->append(item);
    funcItems->append(FuncItem()); //draw a space line.

    const int count = 2;
    *nbF = count;

    return &((*funcItems)[0]);
}


void _beNotified(SCNotification *notifyCode)
{
    switch (notifyCode->nmhdr.code)
    {
    case NPPN_SHUTDOWN: {
        break;
    }
    case NPPN_READY: {
        NPPPySide::instance()->UpdateMenu();
        break;
    }
    default:
        return;
    }
}


LRESULT _messageProc(UINT /*Message*/, WPARAM /*wParam*/, LPARAM /*lParam*/)
{
    return TRUE;
}


BOOL _isUnicode()
{
    return TRUE;
}


NPPPySide *NPPPySide::instance(const NppData *nppData)
{
    static NPPPySide s_NPPPySide(*nppData);
    return &s_NPPPySide;
}

static WNDPROC lastLone = nullptr;
LRESULT CALLBACK MyCallback(HWND hwnd, UINT msg, WPARAM w, LPARAM l){
    switch (msg) {
    case WM_COMMAND: {
        unsigned int cmd = static_cast<unsigned int>(LOWORD(w));
        NPPPySide::instance()->CommandSwitch(cmd);
        break;
    }
    case WM_QUIT:
    case WM_CLOSE:
    case WM_DESTROY: {
        NPPPySide::instance()->Destroy();
        break;
    }
    }
    return lastLone(hwnd, msg, w, l);
}

LRESULT CALLBACK modwndproc(HWND hwnd, UINT msg, WPARAM w, LPARAM l){
    switch (msg) {
    case WM_QUIT: {
        break;
    }
    case WM_CLOSE: {
        PostQuitMessage(0);
        return true;
    }
    case WM_DESTROY:
        return true;
    }
    return DefWindowProcW(hwnd, msg, w, l);
}

extern "C" PyObject *PyInit_AppLib();
NPPPySide::NPPPySide(const NppData &nppData) : nppData(nppData)
{
    lastLone = reinterpret_cast<WNDPROC>(SetWindowLongW(nppData._nppHandle, GWL_WNDPROC, reinterpret_cast<LONG>(MyCallback)));
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));

    wchar_t *wstrs = GetCommandLineW();
    int count = 0;
    wchar_t **cmdLineAry = CommandLineToArgvW(wstrs, &count);
    if (count == 0)
        return;

    char **utf8Args = new char*[static_cast<unsigned int>(count)];
    for (int i = 0; i < count; ++i) {
        std::string stdArg = QString::fromWCharArray(cmdLineAry[i]).toStdString();
        char *temp = new char[stdArg.size() + 1];
        memset(temp, 0, stdArg.size() + 1);
        memcpy_s(temp, stdArg.size() + 1, stdArg.data(), stdArg.size());
        utf8Args[i] = temp;
    }

    QString pathNow = QDir::currentPath() + "/plugins/NPPPySide";

    QString path = pathNow + "/Lib/site-packages/PySide2/plugins/platforms";
    qputenv("QT_QPA_PLATFORM_PLUGIN_PATH", path.toLocal8Bit());

    QApplication *app = new QApplication(count, utf8Args);
    QString line = app->arguments().join(",");

    QTimer *timer = new QTimer();
    timer->setInterval(50);
    QObject::connect(timer, &QTimer::timeout, app, [&]{
        app->processEvents();
    });
    timer->start();

    WNDCLASSEXW wcex;
    memset(&wcex, 0, sizeof(WNDCLASSEXW));
    wcex.cbSize = sizeof(WNDCLASSEXW);
    wcex.lpfnWndProc = modwndproc;
    wcex.lpszClassName = L"modwnd";
    RegisterClassEx(&wcex);
    HINSTANCE hinc = reinterpret_cast<HINSTANCE>(GetWindowLongW(nppData._nppHandle, GWL_HINSTANCE));

    int widgetWidget = 800;
    int widgetHeight = 600;
    int width = GetSystemMetrics(SM_CXSCREEN);
    int height= GetSystemMetrics(SM_CYSCREEN);

    hwnd = CreateWindowExW(WS_EX_TOOLWINDOW,
                           L"modwnd", L"NPPPySide",
                           WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
                           (width - widgetWidget) / 2, (height - widgetHeight) / 2, widgetWidget, widgetHeight,
                           nppData._nppHandle, nullptr, hinc, nullptr);

    window = QWindow::fromWinId(reinterpret_cast<WId>(hwnd));

    parentWidget = new QWidget();
    parentWidget->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    parentWidget->setAttribute(Qt::WA_NativeWindow);
    parentWidget->setAttribute(Qt::WA_DontCreateNativeAncestors);
    parentWidget->setFixedSize(window->size());
    QWindow *now = QWindow::fromWinId(parentWidget->winId());
    now->setParent(window);
    now->setPosition(0, 0);
    window->hide();
    parentWidget->show();

    Py_SetPythonHome(pathNow.toStdWString().data());
    Py_Initialize();
    PyInit_AppLib();

    nppPythonObj = new NPPPythonObj();
    widgetMgrUi = new NPPPySideMgrUI();
    widgetMounted = nullptr;
}

void NPPPySide::CommandSwitch(unsigned int menuId)
{
    auto itr = this->mapMenuScript.find(menuId);
    if (itr == this->mapMenuScript.end())
        return;

    QString fileScript = itr.value().second;
    qDebug() << "script:" << fileScript;

    FILE *pFile = nullptr;
    _wfopen_s(&pFile, fileScript.toStdWString().data(), L"r");

    PyTypeObject* pyTpyeObj = Shiboken::SbkType<NPPPythonObj>();
    PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(pyTpyeObj), nppPythonObj);
    po->ob_refcnt++;
    PyObject *module = PyImport_AddModule("__main__");
    PyModule_AddObject(module, "NPPPySide", po);

    PyRun_SimpleFile(pFile, fileScript.toStdString().data());
    fclose(pFile);
}

void NPPPySide::Destroy()
{
    if (widgetMgrUi != nullptr) {
        widgetMgrUi->deleteLater();
        widgetMgrUi = nullptr;
    }

    if (window != nullptr) {
        window->destroy();
        window = nullptr;
    }

    if (hwnd != nullptr) {
        DestroyWindow(hwnd);
        hwnd = nullptr;
        Py_Finalize();
    }
}

void NPPPySide::SetMgrVisible(bool visible)
{
    if (visible) {
        parentWidget->setFixedSize(widgetMgrUi->size());
        widgetMgrUi->setParent(parentWidget);
        widgetMgrUi->move(0, 0);
        parentWidget->move(0, 0);
        window->resize(widgetMgrUi->size());
    }

    widgetMgrUi->setVisible(visible);
}

void NPPPySide::ShowPySideWindow()
{
    RECT rect;
    GetWindowRect(nppData._nppHandle, &rect);
    QRect rectNpp(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
    QRect rectWindow = window->geometry();
    rectWindow.moveCenter(rectNpp.center());
    window->setPosition(rectWindow.topLeft());

    window->show();

    SetForegroundWindow(reinterpret_cast<HWND>(window->winId()));
    EnableWindow(nppData._nppHandle, FALSE);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (msg.message == WM_QUIT || msg.message == WM_CLOSE || msg.message == WM_DESTROY) {
            break;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    window->hide();

    widgetMgrUi->hide();

    if (widgetMounted != nullptr) {
        widgetMounted->hide();
        widgetMounted->deleteLater();
        widgetMounted = nullptr;
    }

    EnableWindow(nppData._nppHandle, TRUE);
}

void NPPPySide::MountWidget(QWidget *widget)
{
    widget->hide();
    parentWidget->hide();

    QSize size = widget->size();
    window->resize(size);
    parentWidget->setFixedSize(size);
    widget->setParent(parentWidget);
    widget->move(0, 0);
    parentWidget->move(0, 0);

    NPPPySide::instance()->SetMgrVisible(false);
    widgetMounted = widget;
    widget->show();
    parentWidget->show();

    NPPPySide::instance()->ShowPySideWindow();
}

void NPPPySide::UpdateMenu()
{
    //找到主插件的菜单
    long menuHandle = ::SendMessageW(this->nppData._nppHandle, NPPM_GETMENUHANDLE, NPPPLUGINMENU, 0);
    HMENU hmenuPlugin = reinterpret_cast<HMENU>(menuHandle);
    int pluginMenuCount = GetMenuItemCount(hmenuPlugin);

    HMENU hmenuSubmenu = nullptr;
    for (int i = 0; i < pluginMenuCount; ++i)
    {
        wchar_t menuStr[64] = { 0 };
        GetMenuStringW(hmenuPlugin, static_cast<UINT>(i), menuStr, 64, MF_BYPOSITION);
        if (wcscmp(menuStr, Plugin_Name) == 0) {
            hmenuSubmenu = GetSubMenu(hmenuPlugin, i);
            break;
        }
    }
    if (hmenuSubmenu == nullptr)
        return;

    //移除所有子菜单重建
    for (auto itr = this->mapMenuScript.begin(); itr != this->mapMenuScript.end(); ++itr) {
        ::RemoveMenu(hmenuSubmenu, itr.key(), MF_BYCOMMAND);
    }
    this->mapMenuScript.clear();

    //根据配置添加子菜单
    QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Xairy.Co.Ltd", "NPPPSide");
    QMap<int, QString> groupsSort;
    QStringList groups = settings.childGroups();
    for (int i = 0; i < groups.count(); ++i) {
        const QString &group = groups.at(i);
        settings.beginGroup(group);
        int index = settings.value("index").toInt();
        groupsSort.insert(index, group);
        settings.endGroup();
    }

    int index = 0;
    for (auto itr = groupsSort.begin(); itr != groupsSort.end(); ++itr, index++) {
        settings.beginGroup(itr.value());
        QString scriptName = settings.value("name").toString();
        QString scpritFile = settings.value("file").toString();
        unsigned int mid = static_cast<unsigned int>(50000 + index);
        ::AppendMenuW(hmenuSubmenu, MF_STRING | MF_ENABLED, mid, scriptName.toStdWString().data());
        this->mapMenuScript.insert(mid, QPair<QString, QString>(scriptName, scpritFile));
        settings.endGroup();
    }


    //执行主脚本
    QString fileScript = QFileInfo(settings.fileName()).absolutePath() + "/master.py";
    FILE *pFile = nullptr;
    _wfopen_s(&pFile, fileScript.toStdWString().data(), L"r");

    PyTypeObject* pyTpyeObj = Shiboken::SbkType<NPPPythonObj>();
    PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(pyTpyeObj), nppPythonObj);
    po->ob_refcnt++;
    PyObject *module = PyImport_AddModule("__main__");
    PyModule_AddObject(module, "NPPPySide", po);
    PyRun_SimpleFile(pFile, fileScript.toStdString().data());
    fclose(pFile);
}

void NPPPySide::OpenMasterFile()
{
    QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Xairy.Co.Ltd", "NPPPSide");
    QString filePath = QFileInfo(settings.fileName()).absolutePath() + "/master.py";

    std::wstring filePathUtf8 = filePath.toStdWString();
    const wchar_t *w = filePathUtf8.data();

    ::SendMessage(nppData._nppHandle, NPPM_DOOPEN, 0, (LPARAM)w);
}

void NPPPySide::SendMessageToNPP(UINT Msg, WPARAM wParam, LPARAM lParam)
{
    ::SendMessage(nppData._nppHandle, Msg, wParam, lParam);
}

void NPPPySide::SendMessageToScintilla(UINT Msg, WPARAM wParam, LPARAM lParam)
{
    int which = -1;
    ::SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&which);
    if (which == -1)
        return;
    HWND curScintilla = (which == 0)?nppData._scintillaMainHandle:nppData._scintillaSecondHandle;
    ::SendMessage(curScintilla, SCI_SETTEXT, wParam, lParam);
}

